import asyncio


class ReceiveBase:
    def __init__(self, recv_id):
        # self._match_map = match_map
        self.__msg_box = []
        self.__msg_future = None
        self.__wait_future = None
        self.__id = recv_id
        self.__data = {}
        self.__receive_run_list = []
        self.__run_count = 0

    def __receive_msg_box(self, match_map):
        for msg in self.__msg_box:
            if type(msg) == tuple:
                if msg[0] in match_map:
                    if len(msg) >= 2:
                        recv = match_map[msg[0]](msg[1])
                    else:
                        recv = match_map[msg[0]]()
                    self.__msg_box.remove(msg)
                    return True, recv
                else:
                    if 'default' in match_map:
                        recv = match_map['default'](msg)
                        self.__msg_box.remove(msg)
                        return True, recv
            else:
                self.__msg_box.remove(msg)
        return False, None

    async def run(self, match_map):
        run_count = self.__run_count + 1
        self.__run_count = run_count
        self.__receive_run_list.append(run_count)
        while self.__wait_future is not None:
            if await self.__wait_future == run_count:
                break
        loop = asyncio.get_running_loop()
        if self.__wait_future is None:
            self.__wait_future = loop.create_future()
        res_msg = await self.__receive_msg(match_map)
        del self.__receive_run_list[0]
        if len(self.__receive_run_list) > 0:
            self.__wait_future.set_result(self.__receive_run_list[0])
            self.__wait_future = loop.create_future()
        else:
            self.__wait_future.set_result(True)
            self.__wait_future = None
        return res_msg

    async def __receive_msg(self, match_map):
        box_match, recv = self.__receive_msg_box(match_map)
        if not box_match:
            while True:
                loop = asyncio.get_running_loop()
                self.__msg_future = loop.create_future()
                msg = await self.__msg_future
                self.__msg_future = None
                if type(msg) == tuple:
                    if msg[0] in match_map:
                        if len(msg) >= 2:
                            recv = match_map[msg[0]](msg[1])
                        else:
                            recv = match_map[msg[0]]()
                        break
                    else:
                        if 'default' in match_map:
                            recv = match_map['default'](msg)
                            break
                        else:
                            self.__msg_box.append(msg)
        return recv

    def send_msg(self, obj):
        if self.__msg_future is not None:
            try:
                self.__msg_future.set_result(obj)
            except asyncio.InvalidStateError:
                self.__msg_box.append(obj)
        else:
            self.__msg_box.append(obj)
        return True

    def get_state(self, key):
        if key not in self.__data:
            return None
        return self.__data[key]

    def set_state(self, key, val):
        self.__data[key] = val

    def all_state(self):
        return self.__data.copy()

    def get_id(self):
        return self.__id

    def reset(self):
        # self.__msg_box = []
        if self.__msg_future is not None:
            del self.__msg_future
        self.__msg_future = None


class Receive(ReceiveBase):

# match_map = {
    # match_key: (lambda ...)
    # }
    __receive_list = {}

    @classmethod
    def __add_receive_id(cls, recv_id, obj):
        cls.__receive_list[recv_id] = obj

    @classmethod
    def __del_receive_id(cls, recv_id):
        del cls.__receive_list[recv_id]

    @classmethod
    def __get_receive_id(cls, recv_id):
        if recv_id in cls.__receive_list:
            return cls.__receive_list[recv_id]
        return None

    @classmethod
    def init_recv(cls,recv_id, match_id):
        if recv_id in cls.__receive_list:
            raise RecvAlreadyExistsException
        obj = Receive(recv_id, match_id)
        cls.__add_receive_id(recv_id, obj)
        return obj

    @classmethod
    async def start_recv(cls, recv_id):
        if recv_id not in cls.__receive_list:
            raise RecvNotExistsException
        await cls.__get_receive_id(recv_id).start()

    @classmethod
    def stop_recv(cls, recv_id, reason):
        if recv_id not in cls.__receive_list:
            raise RecvNotExistsException
        cls.__get_receive_id(recv_id).stop(reason)
        print("recv_obj stop, reason:", reason)
        return True

    @classmethod
    def del_recv(cls, recv_id):
        if recv_id not in cls.__receive_list:
            raise RecvNotExistsException
        if cls.__get_receive_id(recv_id).is_running():
            raise RecvOnRunningException
        cls.__del_receive_id(recv_id)
        return True

    @classmethod
    async def run_recv(cls, recv_id, match_map):
        if recv_id not in cls.__receive_list:
            raise RecvNotExistsException
        return await cls.__get_receive_id(recv_id).run(match_map)

    @classmethod
    def send_recv_msg(cls, recv_id, msg):
        if recv_id not in cls.__receive_list:
            raise RecvNotExistsException
        if not cls.__get_receive_id(recv_id).is_running():
            raise RecvNotRunningException
        return cls.__get_receive_id(recv_id).send_msg(msg)

    @classmethod
    def id_get_state(cls, recv_id, key):
        if recv_id not in cls.__receive_list:
            raise RecvNotExistsException
        if not cls.__get_receive_id(recv_id).is_running():
            raise RecvNotRunningException
        return cls.__get_receive_id(recv_id).get_state(key)

    @classmethod
    def id_set_state(cls, recv_id, key, val):
        if recv_id not in cls.__receive_list:
            raise RecvNotExistsException
        if not cls.__get_receive_id(recv_id).is_running():
            raise RecvNotRunningException
        cls.__get_receive_id(recv_id).set_state(key, val)
        return True

    @classmethod
    def show_all_state(cls, recv_id):
        if recv_id not in cls.__receive_list:
            raise RecvNotExistsException
        if not cls.__get_receive_id(recv_id).is_running():
            raise RecvNotRunningException
        return cls.__get_receive_id(recv_id).all_state()

    def __init__(self, recv_id, match_map):
        super(Receive, self).__init__(recv_id)
        self.__match_map = match_map
        self.__match_map['$stop_r$run$'] = lambda reason: reason
        self.__state = False

    async def start(self):
        self.reset()
        self.__state = True
        await self.__loop_recv()

    async def __loop_recv(self):
        while self.__state:
            await self.run(self.__match_map)

    def stop(self, reason):
        self.__state = False
        Receive.send_recv_msg(self.get_id(), ("$stop_r$run$", reason))

    def is_running(self):
        return self.__state


class RecvAlreadyExistsException(Exception):
    def __init__(self, *args):
        self.args = args


class RecvNotExistsException(Exception):
    def __init__(self, *args):
        self.args = args


class RecvOnRunningException(Exception):
    def __init__(self, *args):
        self.args = args


class RecvNotRunningException(Exception):
    def __init__(self, *args):
        self.args = args


